/***************************************************************************
 *   Copyright (C) 2007 by Sebastian Bilski, Lance Cooper, Jeff Rebacz,
 *   									and Alex Zelenskiy
 *   sbilski@iit.edu
 *   lance.m.cooper@gmail.com
 *   jeff.rebacz@gmail.com
 *   voiceofanarchy@gmail.com
 *
 *	This file is part of connect3
 *
 *   connect3 is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License, version 2,
 *    as published by the Free Software Foundation
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc., 
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. 
 ***************************************************************************/

import java.awt.Color;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Iterator;


public class Protocol {
	Socket sock = null;
	private boolean open = false;
	GameLogic logic = null;
	DataInputStream input = null;
	DataOutputStream output = null;
	private GameBoard board = null;
	private int client_turn_order = -1;
	private Player player = null;
	public static enum MESSAGE_TYPES  {PLAYER_INFO, DISCONNECT, TURN_ROTATE, ILLEGAL_OPERATION, 
																  MOVE, READY, WON, STALEMATE, RESET, PLAYER_LIST, PLAYER_COUNT,
																  CHAT, GRIDSIZE, STATUS, NONE};
	public MESSAGE_TYPES last = MESSAGE_TYPES.NONE;
	private GamePiece piece = null;
	private Player lp = null;
	private int wonId = 0;
	private int dcdId = 0;
	private String chat = "";
	
	private boolean isServer = false;
	
	public Protocol(Socket sock, GameLogic logic, GameBoard board) throws IOException {
		this.sock = sock;
		this.logic = logic;
		this.board  = board;
		InputStream in = sock.getInputStream();
		OutputStream out = sock.getOutputStream();
		output = new DataOutputStream(out);
		input = new DataInputStream(in);
		open = true;
	}
	
	/**Called repeatedly by the server to read protocol messages from the client.
	 * Updates the game logic object in a thread safe manner according to the message recieved. 
	 * @return true if the client is still connected.
	 */
	public boolean readProtoMessage()
	{
		if(!open)
			return false;
		piece = null;
		try {
			int mess_type = input.readInt();
			if(MESSAGE_TYPES.PLAYER_INFO.ordinal() == mess_type)
			{
				lp = readPlayer();
				last = MESSAGE_TYPES.PLAYER_INFO;
				if(client_turn_order != -1)
					return true;
				client_turn_order = logic.addPlayer(lp);
				player = lp;
			}
			else if(MESSAGE_TYPES.DISCONNECT.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.DISCONNECT;
				int id = input.readInt();
				if(isServer)
					dcdId = player.getTurnOrder();
				else
					dcdId = id;
				if(isServer)
					return false;
			}
			else if(MESSAGE_TYPES.MOVE.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.MOVE;
				int x = input.readInt();
				int y = input.readInt();

				piece = new GamePiece(x, y, player.getColor());
				if(GameLogic.checkMove(board, x) && (!isServer || (ServerController.isGameInProgress() && player.getTurnOrder() == logic.getTurnOrder())))
				{
					board.makeMove(piece, x);
				}
				else
				{
					piece = null;
					sendIllegal();
				}
			}
			else if(MESSAGE_TYPES.PLAYER_LIST.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.PLAYER_LIST;
				sendPlayerList();
			}
			else if(MESSAGE_TYPES.PLAYER_COUNT.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.PLAYER_COUNT;
				output.writeInt(logic.playerList.size());
			}
			else if(MESSAGE_TYPES.TURN_ROTATE.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.TURN_ROTATE;
			}
			else if(MESSAGE_TYPES.WON.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.WON;
				wonId = input.readInt();
			}
			else if(MESSAGE_TYPES.STALEMATE.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.STALEMATE;
			}
			else if(MESSAGE_TYPES.RESET.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.RESET;
				board.reset();
				logic.reset();
			}
			else if(MESSAGE_TYPES.CHAT.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.CHAT;
				chat = input.readUTF();
				if(isServer)
					chat = player.getUserName() + ": " +chat;
				if(!chat.endsWith("\n"))
					chat += "\n";
			}
			else if(MESSAGE_TYPES.READY.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.READY;
			}
			else if(MESSAGE_TYPES.GRIDSIZE.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.GRIDSIZE;
				output.writeInt(board.getXDimension());
				output.writeInt(board.getYDimension());
			}
			else if(MESSAGE_TYPES.STATUS.ordinal() == mess_type)
			{
				last = MESSAGE_TYPES.STATUS;
				if(ServerController.isGameInProgress())
					output.writeInt(1);
				else
					output.writeInt(0);
			}
			else
			{
				System.err.println("got unknown protocol message of type " + mess_type);
				return false;
			}
		} catch (IOException e) {
			return false;
		}
		return true;
	}
	
	private void sendIllegal() {
		
	}
	
	private Player readPlayer() throws IOException
	{
		String name = input.readUTF();
		int rgb = input.readInt();
		Color col = new Color(rgb);
		return new Player(name, col);
	}
	
	private void sendPlayerData(Player p) throws IOException
	{
		output.writeUTF(p.getUserName());
		output.writeInt(p.color.getRGB());
	}

	public void sendPlayer(Player p)
	{
		try {
			output.writeInt(MESSAGE_TYPES.PLAYER_INFO.ordinal());
			sendPlayerData(p);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendMove(GamePiece p)
	{
		try {
			output.writeInt(MESSAGE_TYPES.MOVE.ordinal());
			output.writeInt(p.getX());
			output.writeInt(p.getY());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendChat(String s)
	{
		try {
			output.writeInt(MESSAGE_TYPES.CHAT.ordinal());
			output.writeUTF(s);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendWon(int id)
	{
		try {
			output.writeInt(MESSAGE_TYPES.WON.ordinal());
			output.writeInt(id);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendStalemate()
	{
		try {
			output.writeInt(MESSAGE_TYPES.STALEMATE.ordinal());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendReady()
	{
		try {
			output.writeInt(MESSAGE_TYPES.READY.ordinal());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendDisconnect(int id)
	{
		try {
			output.writeInt(MESSAGE_TYPES.DISCONNECT.ordinal());
			output.writeInt(id);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	/**gets the size of the game board from the server
	 * @return an array [x,y]
	 */
	public int[] getBoardSize() throws IOException
	{
		int[] xy = new int[2];
		try {
			output.writeInt(MESSAGE_TYPES.GRIDSIZE.ordinal());
			xy[0] = input.readInt();
			xy[1] = input.readInt();
		} catch (IOException e) {
			e.printStackTrace();
			throw e;
		}
		return xy;
	}
	
	public int getPlayerId()
	{
		return player.getTurnOrder();
	}
	
	public String getChat()
	{
		return chat;
	}
	
	public void sendReset()
	{
		try {
			output.writeInt(MESSAGE_TYPES.RESET.ordinal());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public GamePiece getPiece()
	{
		return piece;
	}
	
	public Player getPlayer()
	{
		return lp;
	}
	
	public int getDisconnect()
	{
		return dcdId;
	}

	public void sendTurnRotate() {
		try {
			output.writeInt(MESSAGE_TYPES.TURN_ROTATE.ordinal());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendPlayerList()
	{
		try {
			output.writeInt(logic.playerList.size());
		for(Iterator<Player> it = logic.playerList.iterator(); it.hasNext();)
		{
			sendPlayerData(it.next());
		}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public int getWinner()
	{
		return wonId;
	}
	
	/**should only be called by newly connecting clients. retrieves the current player listing for the server
	 */
	public ArrayList<Player> getPlayers()
	{
		ArrayList<Player> ret = new ArrayList<Player>();
		try {
			output.writeInt(MESSAGE_TYPES.PLAYER_LIST.ordinal());
			int count = input.readInt();
			for(int i = 0; i < count; i++)
			{
				Player p = readPlayer();
				ret.add(p);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return ret;
	}
	
	public int getPlayerCount()
	{
		try {
			output.writeInt(MESSAGE_TYPES.PLAYER_COUNT.ordinal());
			return input.readInt();
		} catch (IOException e) {
			e.printStackTrace();
			return 0;
		}
	}
	
	public int getStatus()
	{
		try {
			output.writeInt(MESSAGE_TYPES.STATUS.ordinal());
			return input.readInt();
		} catch (IOException e) {
			e.printStackTrace();
			return 1;
		}
	}
	
	public void setServer()
	{
		isServer = true;
	}
	
	/**shut down the socket associated with this protocol
	 */
	public void close()
	{
		try {
			input.close();
			output.close();
			sock.close();
		} catch (IOException e) {
		}
		open = false;
	}

	public GameBoard getBoard() {
		return board;
	}

	public void setBoard(GameBoard board) {
		this.board = board;
	}
}
